package component;

import java.util.ArrayList;

import Prism.core.AbstractImplementation;
import Prism.core.Event;
import Prism.core.PrismConstants;
import basicTypes.NumberDurationTime;
import basicTypes.OnlineUserRecord;
import basicTypes.Service;

public class OnLineDatabase extends AbstractImplementation {
	// prov Name(Event Name)
	public static final String provAddUser = "addUser";
	public static final String provDeleteUser = "deleteUser";
	public static final String provUpdateUser = "updateUser";
	public static final String provUpdateOffLineDB = "updateOffLineDB";
	public static final String provGetUserData = "getUserData";
	public static final String provAddService = "addService";
	public static final String provRemoveService = "removeService";
	public static final String provAddNewCall = "addNewCall";
	public static final String provRemoveCall = "removeCall";
	
    //users: \set OnLineRecordType;
	ArrayList<OnlineUserRecord> users;
	
	//servicesAccessed: \set Service;
	ArrayList<Service> servicesAccessed;
	
	//numbersCalled: \set NumberDurationTime;
	ArrayList<NumberDurationTime> numbersCalled; 
	
	//lastCopyAt: Time;
	int lastCopyAt;
	
	//userByPhoneNumber: PhoneNumber -> OnLineRecordType;
	public OnlineUserRecord userByPhoneNumber(String phoneNumber) {
		int size = users.size();
		String no;
		// Find user by PhoneNumber
		for(int i=0; i<size; i++) {
			// if found, return it
			no = users.get(i).getPhoneNumber();
			if(no.equals(phoneNumber)) return users.get(i);
		}
		
		// in case of no user found
		return null; 
	}
	
	//get user index by phone number
	public int userIndexByPhoneNumber(String phoneNumber) {
		int size = users.size();
		String no;
		// Find user by PhoneNumber
		for(int i=0; i<size; i++) {
			// if found, return it
			no = users.get(i).getPhoneNumber();
			if(no.equals(phoneNumber)) return i;
		}
		
		// in case of no user found
		return -1; 
	}
	
	//serviceAccessedByPhoneNumber: PhoneNumber -> Service;
	public Service serviceAccessedByPhoneNumber(String phoneNumber) {
		int size = servicesAccessed.size();
		String no;
		// Find Service by PhoneNumber
		for(int i=0; i<size; i++) {
			// if found, return it
			no = servicesAccessed.get(i).getPhoneNumber();
			if(no.equals(phoneNumber)) return servicesAccessed.get(i);
		}
		
		// in case of no Service found
		return null; 
	}
	
	// get service index by phone number
	public int serviceAccessedIndexByPhoneNumber(String phoneNumber) {
		int size = servicesAccessed.size();
		String no;
		// Find Service by PhoneNumber
		for(int i=0; i<size; i++) {
			// if found, return it
			no = servicesAccessed.get(i).getPhoneNumber();
			if(no.equals(phoneNumber)) return i;
		}
		
		// in case of no Service found
		return -1;
	}

	//numberCalledByPhoneNumber: PhoneNumber -> NumberDurationTime;
	public NumberDurationTime numberCalledByPhoneNumber(String phoneNumber) {
		int size = numbersCalled.size();
		String no;
		// Find numbersCalled by PhoneNumber
		for(int i=0; i<size; i++) {
			// if found, return it
			no = numbersCalled.get(i).getPhoneNumber();
			if(no.equals(phoneNumber)) return numbersCalled.get(i);
		}
		
		// in case of no numbersCalled found
		return null; 
	}
	
	//timeOfCall: NumberDurationTime -> Time;
	public String timeOfCall(NumberDurationTime NDT) {return NDT.getTime();}
	
	//numberCalledByTime: Time -> NumberDurationTime;
	public NumberDurationTime numberCalledByTime(String time) {
		int size = numbersCalled.size();
		String t;
		// Find numbersCalled by time
		for(int i=0; i<size; i++) {
			// if found, return it
			t = numbersCalled.get(i).getTime();
			if(t.equals(time)) return numbersCalled.get(i);
		}
		
		// in case of no numbersCalled found
		return null; 
	}

	@Override
	public void start() {
		super.start();
		
		// Initialize Variables
		users = new ArrayList<OnlineUserRecord>();
		servicesAccessed = new ArrayList<Service>();
		numbersCalled = new ArrayList<NumberDurationTime>(); 
		lastCopyAt = 1;
		
		users.add(new OnlineUserRecord("25000","Kwan Park","100","2132548799"));
		users.add(new OnlineUserRecord("23300","USC","5534","1231231231"));
		users.add(new OnlineUserRecord("21200","CSCI","1002","2342342342"));
		users.add(new OnlineUserRecord("24400","NENO","344","3453453453"));
	}
	
	// Check invariant property
	public boolean checkInvariant() {
		
		if(    users.size()>=0 //#users \eqgreater 0; 
			&& servicesAccessed.size()>=0 //#servicesAccessed \eqgreater 0;
			&& numbersCalled.size()>=0 //#numbersCalled \eqgreater 0;
			&& lastCopyAt >0 //lastCopyAt \greater 0;
		) return true;
		
		return false;
	}
	
	@Override
	public void handle(Event e) {
	
		// Handle of request of provAddUser
		if(e.getName().equals(provAddUser)) {
			
			OnlineUserRecord tempRecord = new OnlineUserRecord(
					(String) e.getParameter("time"),
					(String) e.getParameter("name"),
					(String) e.getParameter("amount"),
					(String) e.getParameter("phoneNumber")
					);
			//pre (userByPhoneNumber(id) \not_in users);
			if(userByPhoneNumber((String)e.getParameter("phoneNumber"))==null) {
				//post (userByPhoneNumber(id) \in users) \and (#~users = #users+1);
				users.add(tempRecord);
			}
		}
		// Handle of request of provDeleteUser
		else if(e.getName().equals(provDeleteUser)) {
			String id = (String) e.getParameter("phoneNumber");
			
			//pre (userByPhoneNumber(id) \in users);
			if(userByPhoneNumber((String)e.getParameter("phoneNumber"))!=null) {
				int index = userIndexByPhoneNumber(id);
				if(index>=0) {
					//post (userByPhoneNumber(id) \not_in users) \and (#~users = #users-1);
					users.remove(index);
				}
			}
			
		}
		// Handle of request of provUpdateUser
		else if(e.getName().equals(provUpdateUser)) {
			OnlineUserRecord tempRecord = new OnlineUserRecord(
					(String) e.getParameter("time"),
					(String) e.getParameter("name"),
					(String) e.getParameter("amount"),
					(String) e.getParameter("phoneNumber")
					);
		
			//pre (userByPhoneNumber(id) \in users);
			if(userByPhoneNumber((String)e.getParameter("phoneNumber"))!=null) {
				int index = userIndexByPhoneNumber((String)e.getParameter("phoneNumber"));
				//post (userByPhoneNumber(id)=rec);
				users.set(index, tempRecord);
			}
			
		}
		// Handle of request of provUpdateOffLineDB
		else if(e.getName().equals(provUpdateOffLineDB)) {
			
			//pre (lastCopyAt+86400) \eqless now;//one day difference
			if( (lastCopyAt+86400) < (System.currentTimeMillis()/1000) )
			{
				//post (lastCopyAt=now);
				lastCopyAt = (int)(System.currentTimeMillis()/1000);
			}
		}
		// Handle of request of provGetUserData
		else if(e.getName().equals(provGetUserData)) {
			//pre (userByPhoneNumber(id) \in users);
			if(userByPhoneNumber((String)e.getParameter("phoneNumber"))!=null) {
				//post \result = userByPhoneNumber(id);
				OnlineUserRecord tempRecord = userByPhoneNumber((String)e.getParameter("phoneNumber"));
				Event n = new Event(provGetUserData, PrismConstants.REPLY);
				n.addParameter("name", tempRecord.getName());
				n.addParameter("phoneNumber", tempRecord.getPhoneNumber());
				n.addParameter("amount", tempRecord.getAmount());
				n.addParameter("time", tempRecord.getTime());
				this.send(n);
			}
		}
		// Handle of request of provAddService
		else if(e.getName().equals(provAddService)) {
			Service tempService = new Service(
				(String) e.getParameter("serviceType"),
				(String) e.getParameter("phoneNumber")
				);

			// pre (userByPhoneNumber(id) \in users)
			// \and (serviceAccessedByPhoneNumber(id) \not_in servicesAccessed);
			if( (userByPhoneNumber((String)e.getParameter("phoneNumber"))!=null)
				&& (serviceAccessedByPhoneNumber((String)e.getParameter("phoneNumber")) == null))
			{
				// post (serviceAccessedByPhoneNumber(id) \in servicesAccessed) 
				// \and (#~servicesAccessed = #servicesAccessed+1);
				servicesAccessed.add(tempService);
			}
		}
		// Handle of request of provRemoveService
		else if(e.getName().equals(provRemoveService)) {
			Service tempService = new Service(
					(String) e.getParameter("serviceType"),
					(String) e.getParameter("phoneNumber")
					);
			
			// pre (userByPhoneNumber(id) \in users) 
			//	\and (serviceAccessedByPhoneNumber(id) \in servicesAccessed);
			if( (userByPhoneNumber((String)e.getParameter("phoneNumber"))!=null)
					&& (serviceAccessedByPhoneNumber((String)e.getParameter("phoneNumber")) != null))
			{
				// post (serviceAccessedByPhoneNumber(id) \not_in servicesAccessed)
				// \and (#~servicesAccessed = #servicesAccessed-1);
				int index = serviceAccessedIndexByPhoneNumber((String)e.getParameter("phoneNumber"));
				servicesAccessed.set(index, tempService);
			}
		}
		// Handle of request of provAddNewCall
		else if(e.getName().equals(provAddNewCall)) {
			NumberDurationTime tempTime = new NumberDurationTime(
					(String) e.getParameter("time"),
					(String) e.getParameter("phoneNumber")
					);
			
			// pre (userByPhoneNumber(id) \in users) 
			// \and (numberCalledByPhoneNumber(id) \not_in numbersCalled);
			if( (userByPhoneNumber((String)e.getParameter("phoneNumber"))!=null)
				&& (numberCalledByPhoneNumber((String)e.getParameter("phoneNumber")) == null))
			{
				// post (numberCalledByPhoneNumber(id) \in numbersCalled)
				// \and (#~numbersCalled = #numbersCalled+1);
				numbersCalled.add(tempTime);
			}
		}
		// Handle of request of provRemoveCall
		else if(e.getName().equals(provRemoveCall)) {
			/*
			NumberDurationTime tempTime = new NumberDurationTime(
					(String) e.getParameter("time"),
					(String) e.getParameter("phoneNumber")
					);
			*/
			
			// pre (userByPhoneNumber(id) \in users) 
			// \and (numberCalledByPhoneNumber(id) \in numbersCalled);
			if( (userByPhoneNumber((String)e.getParameter("phoneNumber"))!=null)
					&& (numberCalledByPhoneNumber((String)e.getParameter("phoneNumber")) != null))
			{
				// post (timeOfCall(numberCalledByPhoneNumber(id)) \eqless t) 
				int inttime;
				int t=Integer.parseInt((String) e.getParameter("time"));
				
				int size = numbersCalled.size();
				for(int i=0; i<size; i++) {
					inttime=Integer.parseInt(timeOfCall(numbersCalled.get(i)));
					if(inttime<t) {
						// \implies (numberCalledByPhoneNumber(id) \not_in numbersCalled);
						numbersCalled.remove(i);
						i--;
					}
				}
				
			}
		}
	}
}
